﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.Progression.GraphModel;
using Microsoft.VisualStudio.Progression.Styles;

namespace E4D.MEFVisualizer
{
    public sealed class MEFToDGML
    {
        #region Fields & Properties
       
        private Dictionary<string , List<Node> > Export { get; set; }
        private Dictionary<string , List<Node> > Import { get; set; }
        private Dictionary<string , Node>     Namespace { get; set; }
        private CompositionContainer MEF { get; set; }

        public Graph DGMLGraph { get; set; }
        public bool IsNamespaceGrouping { get; set;  }
        public string FileName { get; set; }
        public string Path { get; set; }
        #endregion

        public MEFToDGML( CompositionContainer compositionContainer )
        {
            DGMLGraph = new Graph();
            Export     = new Dictionary<string , List<Node> >();
            Import     = new Dictionary<string , List<Node> >();
            Namespace  = new Dictionary<string, Node>();
            MEF        = compositionContainer;

            IsNamespaceGrouping = true;
            
            FileName   = "MEF Visualizer";
            Path       = "../../";
            
        }

        public void WriteToDGMLFile()
        {
            Build();

            var graphXml = DGMLGraph.ToXml();
            
            if ( string.IsNullOrEmpty( Path ) )
                Path = Environment.CurrentDirectory;

            if ( FileName.ToLower().Contains( ".dgml" ) )
                FileName = FileName.Split( '.' )[0]; //TODO:

            var fileName = string.Format( "{0}/{1}.dgml" , Path , FileName );

            using ( var writer = new StreamWriter( fileName) )
            {
                writer.Write( graphXml );
                writer.Flush();
            }
        }
        public void ShowGraph()
        {
            var win = new DGMLViewer();
            
            win.DataContext = this;
            win.Run();

            win.ShowDialog();
        }

        #region Build Methods
        private void Build()
        {
            BuildCategories();
            
            BuildStyle();
            
            BuildGraph();
        }
        private void BuildCategories()
        {
            var AggregateCatalog = GraphCategory.Register( "AggregateCatalog" , GraphMetadata.Default , typeof( Node ) );
            AggregateCatalog.Metadata.Description = "Aggregate Catalog";

            var catalog = GraphCategory.Register("Catalog", GraphMetadata.Default, typeof (Node));
            catalog.Metadata.Description = "MEF Catalog";

            var nameSpace = GraphCategory.Register( "Namespace" , GraphMetadata.Default , typeof( Node ) );
            nameSpace.Metadata.Description = "Namespace";

            var part = GraphCategory.Register( "Part" , GraphMetadata.Default , typeof( Node ) );
            part.Metadata.Description = "MEF Part";
        }
        private void BuildStyle()
        {
            #region Aggregate Catalog Style
            var AggregateCatalogStyle = new ConditionalStyle( DGMLGraph );
            AggregateCatalogStyle.TargetType = typeof( Node );
            AggregateCatalogStyle.GroupLabel = "Aggregate Catalog";

            var AggregateCatalogCondition = new Condition( AggregateCatalogStyle );
            AggregateCatalogCondition.Expression = "HasCategory('AggregateCatalog')";
            AggregateCatalogStyle.Conditions.Add( AggregateCatalogCondition );

            var AggregateCatalogSetterIcon = new Setter( AggregateCatalogStyle , "Icon" );
            AggregateCatalogSetterIcon.Value = "pack://application:,,,/Microsoft.VisualStudio.Progression.GraphControl;component/Icons/Dialog.png"; 
            AggregateCatalogStyle.Setters.Add( AggregateCatalogSetterIcon );

            var AggregateCatalogSetterBackground = new Setter( AggregateCatalogStyle , "Background" );
            AggregateCatalogSetterBackground.Value = "#4A65A0";
            AggregateCatalogStyle.Setters.Add( AggregateCatalogSetterBackground );

            DGMLGraph.Styles.Add( AggregateCatalogStyle );
            #endregion

            #region Catalog Style
            var CatalogStyle = new ConditionalStyle( DGMLGraph );
            CatalogStyle.TargetType = typeof( Node );
            CatalogStyle.GroupLabel = "Catalog";

            var CatalogCondition = new Condition( CatalogStyle );
            CatalogCondition.Expression = "HasCategory('Catalog')";
            CatalogStyle.Conditions.Add( CatalogCondition );

            var CatalogSetterIcon = new Setter( CatalogStyle , "Icon" );
            CatalogSetterIcon.Value = "CodeSchema_Project"; // pack://application:,,,/MEFDGMLVisualizer;component/Icons/TypeCatalog.png";
            CatalogStyle.Setters.Add( CatalogSetterIcon );

            var CatalogSetterBackground = new Setter( CatalogStyle , "Background" );
            CatalogSetterBackground.Value = "#D3DCEF"; 
            CatalogStyle.Setters.Add( CatalogSetterBackground );

            DGMLGraph.Styles.Add( CatalogStyle ); 
            #endregion

            #region Namespace Style
            var NamespaceStyle = new ConditionalStyle( DGMLGraph );
            NamespaceStyle.TargetType = typeof( Node );
            NamespaceStyle.GroupLabel = "Namespace";

            var NamespaceCondition = new Condition( NamespaceStyle );
            NamespaceCondition.Expression = "HasCategory('Namespace')";
            NamespaceStyle.Conditions.Add( NamespaceCondition );

            var NamespaceSetterIcon = new Setter( NamespaceStyle , "Icon" );
            NamespaceSetterIcon.Value = "CodeSchema_Namespace"; // pack://application:,,,/MEFDGMLVisualizer;component/Icons/TypeCatalog.png";
            NamespaceStyle.Setters.Add( NamespaceSetterIcon );

            var NamespaceSetterBackground = new Setter( NamespaceStyle , "Background" );
            NamespaceSetterBackground.Value = "#FF008080";
            NamespaceStyle.Setters.Add( NamespaceSetterBackground );

            DGMLGraph.Styles.Add( NamespaceStyle );
            #endregion

            #region Part Style
            var PartStyle = new ConditionalStyle( DGMLGraph );
            PartStyle.TargetType = typeof( Node );
            PartStyle.GroupLabel = "Part";

            var PartCondition = new Condition( PartStyle );
            PartCondition.Expression = "HasCategory('Part')";
            PartStyle.Conditions.Add( PartCondition );

            var PartSetterIcon = new Setter( PartStyle , "Icon" );
            PartSetterIcon.Value = "CodeSchema_Field";
            PartStyle.Setters.Add( PartSetterIcon );

            DGMLGraph.Styles.Add( PartStyle );
            #endregion
        }
        private void BuildGraph()
        {
            if ( MEF.Catalog is AggregateCatalog )
            {
                BuildAggregateCatalog(MEF.Catalog as AggregateCatalog);
            }
            else
            {
                BuildCatalog( MEF.Catalog );
            }

            BuildPartLinks();
        }
        private void BuildPartLinks()
        {
            foreach (var exp in Export)
            {
                if( Import.ContainsKey(exp.Key) )
                {
                    foreach(var imp in Import[exp.Key] )
                    {
                        foreach (var expNode in exp.Value)
                        {
                            DGMLGraph.Links.Add(
                            new Link( DGMLGraph , expNode , imp , exp.Key , null ) );
                        }
                        
                    }
                }
            }
        }
        private void BuildAggregateCatalog( AggregateCatalog aggregateCatalog )
        {
            var ac = new Node( DGMLGraph , "AggregateCatalog" , "Aggregate Catalog" , GraphCategory.Get( "AggregateCatalog" ) );
            ac.SetValue( GraphProperties.Group , GroupStyle.Expanded );

            foreach (var catalog in aggregateCatalog.Catalogs)
            {
                var n =  new Node( DGMLGraph , CatalogName( catalog ) , CatalogName( catalog ) , GraphCategory.Get( "Catalog" ) );
                n.SetValue( GraphProperties.Group , GroupStyle.Expanded );

                var link = new Link( DGMLGraph , ac , n , string.Empty , GraphCategory.Get( "Catalog" ) );
                link.SetValue( GraphProperties.IsContainment , true );
                DGMLGraph.Links.Add( link );

                BuildParts(n, catalog);
            }


        }
        private void BuildCatalog( ComposablePartCatalog cpc )
        {
            var catalog = new Node( DGMLGraph , Guid.NewGuid().ToString() , CatalogName(cpc) , null );
            catalog.SetValue( GraphProperties.Group , GroupStyle.Expanded );

            BuildParts(catalog, cpc);
           
        }
        private void BuildParts( Node catalog , ComposablePartCatalog cpc )
        {
            foreach (var part in cpc.Parts)
            {
                var n =  new Node( DGMLGraph , part.ToString() , part.ToString() , GraphCategory.Get( "Part" ) );
                Link link = null;

                if( IsNamespaceGrouping )
                {
                    var ns = BuildNamespace(cpc, part, n);
                    link = new Link( DGMLGraph , catalog , ns , string.Empty , GraphCategory.Get( "Part" ) );
                }
                else
                {
                     link = new Link( DGMLGraph , catalog , n , string.Empty , GraphCategory.Get( "Part" ) );
                   
                }
                
                link.SetValue( GraphProperties.IsContainment , true );
                DGMLGraph.Links.Add( link );
               

                #region Build Export Dictionary
                foreach ( var e in part.ExportDefinitions )
                {
                    if ( Export.ContainsKey( e.ContractName ) )
                    {
                        Export[e.ContractName].Add( n );
                    }
                    else
                    {
                        Export.Add( e.ContractName , new List<Node> { n } );
                    }

                } 
                #endregion

                #region Build Import Dictionary
                foreach ( var e in part.ImportDefinitions )
                {
                    if ( Import.ContainsKey( e.ContractName ) )
                    {
                        Import[e.ContractName].Add( n );
                    }
                    else
                    {
                        Import.Add( e.ContractName , new List<Node> { n } );
                    }
                }
                #endregion
            }
        }
        private Node BuildNamespace( ComposablePartCatalog cpc , ComposablePartDefinition part , Node n )
        {
            string nsName = GetNamesapce(part.ToString());
            Node nameSpace = null;
            if ( !Namespace.ContainsKey( nsName ) )
            {
                nameSpace = new Node( DGMLGraph , Guid.NewGuid().ToString() , nsName , GraphCategory.Get( "Namespace" ) );
                nameSpace.SetValue( GraphProperties.Group , GroupStyle.Expanded );
                Namespace.Add(nsName, nameSpace);
            }
            else
            {
                nameSpace = Namespace[nsName];
            }

            var namespaceLink = new Link( DGMLGraph , nameSpace , n , string.Empty , GraphCategory.Get( "Namespace" ) );
            namespaceLink.SetValue( GraphProperties.IsContainment , true );

            DGMLGraph.Links.Add( namespaceLink );

            return nameSpace;
        }
        #endregion

        #region Helper Methods
        private static string CatalogName( ComposablePartCatalog cpc )
        {
            if ( cpc is AggregateCatalog )
                return "AggregateCatalog";

            if ( cpc is DirectoryCatalog )
                return string.Format( "DirectoryCatalog - {0}" , ( cpc as DirectoryCatalog ).FullPath.Split( '/' ).Last() ); ;

            if ( cpc is AssemblyCatalog )
                return string.Format( "AssemblyCatalog - {0}" , ( cpc as AssemblyCatalog ).Assembly.FullName.Split( ',' )[0] );

            if ( cpc is TypeCatalog )
                return "TypeCatalog";

            return string.Format( "Catalog - {0}" , cpc.GetType().Name );
        }
        private string GetNamesapce( string className )
        {
            int index = className.LastIndexOf('.');
            return className.Substring(0, index);
        }
        #endregion
    }
}
